-- icache wrapper
-- this is provided for a place holder until you do the cache labs
-- until then you should just place this file between your fetch stage
-- of your pipeline and your priority mux for main memory.
library ieee;
use ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.ALL;
USE ieee.std_logic_arith.ALL;
use ieee.numeric_std.all;

entity dcache is
  port(
    CLK            : in  std_logic;
    nReset         : in  std_logic;
    link           : in std_logic;

    dMemRead       : in  std_logic;                       -- CPU side
    dMemWrite      : in  std_logic;                       -- CPU side
    dMemWait       : out std_logic;                       -- CPU side
    dMemAddr       : in  std_logic_vector (15 downto 0);  -- CPU side
    dMemDataRead   : out std_logic_vector (31 downto 0);  -- CPU side
    dMemDataWrite  : in  std_logic_vector (31 downto 0);  -- CPU side

    adMemRead      : out std_logic;                       -- arbitrator side
    adMemWrite     : out std_logic;                       -- arbitrator side
    adMemWait      : in  std_logic;                       -- arbitrator side
    adMemAddr      : out std_logic_vector (15 downto 0);  -- arbitrator side
    adMemDataRead  : in  std_logic_vector (31 downto 0);  -- arbitrator side
    adMemDataWrite : out std_logic_vector (31 downto 0);   -- arbitrator side
    adhalt         : in std_logic;
    cpuhalt        : out std_logic;
    
    snoop_read, snoop_write:        in  std_logic;				--external read / write control
    snoop_addr:                in  std_logic_vector(15 downto 0);	--external address
    snoop_valid   : out std_logic;
    --snoop_link:                   in  std_logic;				--external link
    snoop_ReadData : out std_logic_vector(31 downto 0);
    
    --extvalid:                 out std_logic;				--external valid flag
    extwrite:                 out std_logic				--set high when a write hit is taking place
   -- ext_link:                     out std_logic				--d-cache link flag
    );

end dcache;

architecture struct of dcache is
  
component dram is
  port (
    CLK				:	IN	STD_LOGIC;
    nReset : IN STD_LOGIC;
    addr      : IN  STD_LOGIC_VECTOR (3 DOWNTO 0);
    we        : IN  STD_LOGIC;
    writeport : IN  STD_LOGIC_VECTOR (74 DOWNTO 0);
    readport  : OUT STD_LOGIC_VECTOR (74 DOWNTO 0)
  );
end component;

  subtype framedata is std_logic_vector(74 downto 0);
  
  type USE_array is array (0 to 15) of std_logic;
  signal LRU : USE_array;
  
  signal  c_rdata0, c_rdata1, hit_rdata, c_wdata1, c_wdata0, wdata, miss_rdata, snoop_rdata : framedata;
  signal  c_we0, c_we1, allocate_en, hit_en, snoop_en  : std_logic;
  
  signal  valid0, valid1, dirty0, dirty1 : std_logic;
  signal  tag0, tag1, miss_tag, snoop_tag   : std_logic_vector(8 downto 0);
  signal  data0, data1  : std_logic_vector(63 downto 0);
  
  signal hit, miss, miss_set, miss_dirty,  hitset, hit0, hit1, snoop_set, snoop_hit, snoop_hit0, snoop_hit1, conditionalstore, linkregequals : std_logic;
  
  signal startdrain : std_logic;
  signal dMemDataRead_select : std_logic_vector(2 downto 0);
  
  signal drainindex, next_drainindex : std_logic_vector(5 downto 0);
  signal index, proc_index, snoop_index  : std_logic_vector(3 downto 0);
  signal LRU_en, nextLRU : std_logic;
  
  type states is (IDLE, DRAIN, STARTDR, READ0, READ1, MODIFY, WRITE0, WRITE1, FINISH1, FINISH0, 
                    FINISHDRAIN, HALT, SNOOPFINISH0, SNOOPFINISH1, SNOOPWB0, SNOOPWB1);
                    
  signal state, nextstate : states;
  
  signal link_reg : std_logic_vector(15 downto 0);
  signal next_read_buffer, read_buffer : std_logic_vector(31 downto 0);
  signal read_buffer_we, link_invalidate : std_logic;
  
  type MSI is (M, S, I);
  signal status : MSI;
  
  type PRINTFSTATEMENTS is (NOERROR, HERE0, HERE1, HERE2, HERE3);
  signal errors : PRINTFSTATEMENTS;
  
begin


  
  
  
  valid0 <= c_rdata0(74);
  dirty0 <= c_rdata0(73);
  tag0   <= c_rdata0(72 downto 64);
  data0  <= c_rdata0(63 downto 0);
  
  valid1 <= c_rdata1(74);
  dirty1 <= c_rdata1(73);
  tag1   <= c_rdata1(72 downto 64);
  data1  <= c_rdata1(63 downto 0);  
  
  index <= drainindex(4 downto 1) when adhalt = '1' else
           snoop_addr(6 downto 3) when snoop_write = '1' or snoop_read = '1' else
           dMemAddr(6 downto 3);
  proc_index <= dMemAddr(6 downto 3);
  snoop_index <= snoop_addr(6 downto 3);

  D : dram 
    port map (clk	=> CLK, nReset => nReset, addr => index, we => c_we0, writeport => c_wdata0, readport => c_rdata0);    
  D1 : dram 
    port map (clk	=> CLK, nReset => nReset, addr => index, we => c_we1, writeport => c_wdata1, readport => c_rdata1);
    


----CACHE SET WRITE LOGIC-------------------------------------------
  c_we0 <= '1' when hit_en = '1' and hitset = '0' else
           '1' when allocate_en = '1' and miss_set = '0' else
           '1' when snoop_en = '1' and snoop_set = '0' else '0';
           
  c_we1 <= '1' when hit_en = '1' and hitset = '1' else
           '1' when allocate_en = '1' and miss_set = '1' else
           '1' when snoop_en = '1' and snoop_set = '1' else '0';
           
  c_wdata0 <= wdata when hit_en = '1' and hitset = '0' else
              wdata when allocate_en = '1' and miss_set = '0' else 
              wdata when snoop_en = '1' and snoop_set = '0' else (others => '0');
           
  c_wdata1 <= wdata when hit_en = '1' and hitset = '1' else
           wdata when allocate_en = '1' and miss_set = '1' else 
           wdata when snoop_en = '1' and snoop_set = '1' else (others => '0');

----SNOOP signals
  snoop_hit0  <= '1' when tag0 = snoop_addr(15 downto 7) and valid0 = '1' else '0';
  snoop_hit1  <= '1' when tag1 = snoop_addr(15 downto 7) and valid1 = '1' else '0';
  snoop_hit <= snoop_hit0 or snoop_hit1;
  snoop_set <= '1' when snoop_hit1 = '1' else '0' when snoop_hit0 = '1' else '0';
  snoop_tag <= tag0 when snoop_hit0 = '1' else tag1 when snoop_hit1 = '1' else (others => '0');
  snoop_rdata <= c_rdata0 when snoop_hit0 = '1' else c_rdata1 when snoop_hit1 = '1' else (others => '0');
  snoop_ReadData <= snoop_rdata(63 downto 32) when snoop_addr(2) = '1' else snoop_rdata(31 downto 0);
  
                    
  status <= I when snoop_hit = '0' or snoop_rdata(74) = '0' else
            M when snoop_rdata(74 downto 73) = "11" else S when snoop_rdata(74 downto 73) = "10" else 
            I;

-----hit signals
  hit0  <= '1' when tag0 = dMemAddr(15 downto 7) and valid0 = '1' else '0';          
  hit1  <= '1' when tag1 = dMemAddr(15 downto 7) and valid1 = '1' else '0';         
  hit <= hit0 or hit1;
  hitset <= '1' when hit1 = '1' else '0' when hit0 = '1' else '0';
  hit_rdata <= c_rdata1 when hit1 = '1' else c_rdata0 when hit0 = '1' else (others => '0');
-----miss signals  
  miss <= not hit0 and not hit1;

  miss_set <= not LRU(conv_integer(index));
  miss_rdata <= c_rdata1 when miss_set = '1' else c_rdata0 when miss_set = '0' else (others => '0');
  miss_tag <= tag1 when miss_set = '1' else tag0 when miss_set = '0' else (others => '0');
  miss_dirty <= (dirty0 and not miss_set) or (dirty1 and miss_set);

process (nReset, CLK, LRU_en, index, link, dMemRead, read_buffer_we, link_invalidate)
begin
        if (nReset = '0') then
          link_reg <= (others => '0');
          read_buffer <= (others => '0');
           for i in 0 to 15 loop
                          LRU(i) <= '1';
           end loop;
         elsif (rising_edge(CLK)) then
            if (read_buffer_we = '1') then
              read_buffer <= next_read_buffer;
            end if;
            if LRU_en = '1' then
              LRU(conv_integer(index)) <= nextLRU;
            end if;
            if (link = '1' and dMemRead = '1') then
              link_reg <= dMemAddr;
            elsif (link_invalidate = '1') then
              link_reg <= (others => '0');
            end if;
        end if;
end process;



  statereg : process(CLK, nReset)

  begin
    if nReset = '0' then
      state <= IDLE;
    elsif rising_edge(CLK) then
      state <= nextstate;
    end if;
  end process statereg;
  
  linkregequals <= '1' when ((link_reg xnor dMemAddr) = x"FFFF") else '0';
  
  conditionalstore <= link and dMemWrite and 
  linkregequals and 
  ((hit0 and valid0) or 
  (hit1 and valid1));         
           
  next_state : process(state, nextstate, hit, miss, miss_set, hit0, hit1, dirty0, LRU, 
                adMemWait, dMemRead, dMemWrite, dMemAddr, adMemDataRead, dMemDataWrite, index,
                c_rdata0, c_rdata1, drainindex, adhalt, dirty1, tag0, tag1, nextLRU, link, valid0, valid1,
                hit_rdata, miss_rdata, wdata, snoop_rdata, snoop_tag, status, miss_dirty, miss_tag,
                read_buffer, snoop_read, snoop_write, hitset, link_reg, snoop_hit, conditionalstore)
    begin

        adMemAddr <= dMemAddr;
        adMemWrite <= '0';
        adMemRead <= '0';
        startdrain <= '0';
        adMemDataWrite <= (others => '0');
        LRU_en <= '0';
        nextLRU <= LRU(conv_integer(index));
        dMemWait <= '0';
        cpuhalt <= '0';
        nextstate <= IDLE;
        wdata <= (others => '0');
        snoop_valid <= '0';

--write enables, allocate enable is 1 when the final read from ram is complete and cache needs updated
-- hit_en is when cache needs updated on a hit        
        allocate_en <= '0';
        hit_en <= '0';
        snoop_en <= '0';
        errors <= NOERROR;
        next_read_buffer <= (others => '0');
        read_buffer_we <= '0';
        extwrite <= '0';
        link_invalidate <= '0';
        
        
        
---CORRECT IN ANY CASE
        if (link = '1' and dMemWrite = '1') then -- SC
          dMemDataRead_select <= "000";
          if (link_reg = dMemAddr) then -- succeeds
            if  (hit0 = '1' and valid0 = '1') or (hit1 = '1' and valid1 = '1') then
              dMemDataRead_select <= "111";
            end if;
          end if;
        else   
          dMemDataRead_select <= dMemAddr(2) & hit0 & hit1;
        end if;
        

  case state is
--__________________________________________________
    when IDLE =>
        dMemWait <= '0';
        
        if adhalt = '1' then
          nextstate <= STARTDR;
          if (c_rdata0(74 downto 73) = "11") then
            adMemWrite <= '1';
          end if;
          adMemAddr <= c_rdata0(72 downto 64) & drainindex(4 downto 0) & "00";
          adMemDataWrite <= c_rdata0(31 downto 0);
        elsif (snoop_read = '1' or snoop_write = '1') then
----------------------------------------------------------------------------------------
          if (dMemWrite = '1' or dMemRead = '1') then
            dMemWait <= '1';               ----cache is busy
          end if;
          
          if (status = I) then --do nothing
            nextstate <= IDLE;
          elsif (status = S) then
            snoop_valid <= '1';
            if (snoop_read = '1' or (snoop_read = '1' and snoop_write = '1')) then
             --- snoop_valid <= '1';
              nextstate <= IDLE;
            elsif (snoop_write = '1' and snoop_read = '0') then
              snoop_en <= '1';
              wdata <= '0' & snoop_rdata(73 downto 0); --set valid bit to 0
            end if;
          elsif (status = M) then
            nextstate <= SNOOPWB0;
            snoop_valid <= '1';
          end if;
---------------------------------------------------------------------------------------
        elsif (hit = '1' and dMemWrite = '1' and (link = '0' or (link = '1' and conditionalstore = '1'))) then --service write hit
          dMemWait <= '1';
          nextstate <= MODIFY;
          errors <= HERE2;
------------------------------------------------------------------------------------------          
        elsif ((dMemWrite = '1'or dMemRead = '1') and hit = '0') then
          dMemWait <= '1';
          if (link = '1' and dMemWrite = '1' and conditionalstore = '0') then
            errors <= HERE1;
            nextstate <= IDLE;
            dMemWait <= '0';
          elsif (miss_dirty = '1') then
            nextstate <= WRITE0;
          else --if (LRU(conv_integer(index)) = '1') then
            nextstate <= READ0;
          end if;
          
        elsif (dMemRead = '1' and hit = '1') then
          LRU_en <= '1';
          nextLRU <= hitset;
          nextstate <= IDLE;
        else
          nextstate <= IDLE;
        end if;  
--__________________________________________________

    when MODIFY =>
          extwrite <= '1';
          LRU_en <= '1';
          nextLRU <= hitset;
          
          if (adMemWait = '1') then
            dMemWait <= '1';
            nextstate <= MODIFY;
          else
            dMemWait <= '0'; --don't stop proc, only takes 1 cycle
            nextstate <= IDLE;
              
            hit_en <= '1';
            if dMemAddr(2) = '0' then
              wdata <= "11" & hit_rdata(72 downto 32) & dMemDataWrite;
            else
              wdata <= "11" & hit_rdata(72 downto 64) & dMemDataWrite & hit_rdata(31 downto 0);
            end if;
          
            if (link = '1' and conditionalstore = '1') then
              link_invalidate <= '1';
            end if;
          end if;
            

    when READ0 =>
        dMemWait <= '1';
        adMemRead <= '1'; 
        adMemAddr <= dMemAddr(15 downto 3) & "100";
              
        if(adMemWait = '1') then
          nextstate <= READ0;
        elsif (adMemWait = '0') then
          read_buffer_we <= '1';
          next_read_buffer <= adMemDataRead;
          nextstate <= READ1;
        end if;
        
    when READ1 =>
        dMemWait <= '1';
        adMemRead <= '1';
        adMemAddr <= dMemAddr(15 downto 3) & "000";

        if(adMemWait = '1') then
          nextstate <= READ1;
        elsif (adMemWait = '0') then
          if (dMemRead = '1') then
            nextstate <= IDLE;
            LRU_en <= '1';
            nextLRU <= miss_set;
          elsif dMemWrite = '1' then
            nextstate <= MODIFY;
          end if;

          allocate_en <= '1'; --assert c_we0 when miss_set = 0
          wdata <= '1' & '0' & dMemAddr(15 downto 7) & read_buffer  & adMemDataRead; --update second word and set flags
        end if;
      
    when WRITE0 =>
        dMemWait <= '1';
        adMemWrite <= '1';
         
        adMemAddr <= miss_tag & index & "100";
        adMemDataWrite <= miss_rdata(63 downto 32);
              
        if (snoop_read = '1' or snoop_write = '1') then
          nextstate <= IDLE;
        elsif(adMemWait = '1') then
          nextstate <= WRITE0;
        elsif (adMemWait = '0') then
          nextstate <= FINISH0;
        end if;

    when FINISH0 =>
        dMemWait <= '1';
        nextstate <= WRITE1;
        adMemWrite <= '1';
        adMemAddr <= miss_tag & index & "100";
        adMemDataWrite <= miss_rdata(63 downto 32);

    when WRITE1 =>
        dMemWait <= '1';
        adMemWrite <= '1';
        adMemAddr <= miss_tag & index & "000";
        adMemDataWrite <= miss_rdata(31 downto 0);

        if (snoop_read = '1' or snoop_write = '1') then
          nextstate <= IDLE;
        elsif(adMemWait = '1') then
          nextstate <= WRITE1;
        elsif (adMemWait = '0') then
          nextstate <= FINISH1;
        end if;
        
    when FINISH1 =>
          dMemWait <= '1';
          nextstate <= READ0;
          adMemWrite <= '1';
          adMemAddr <= miss_tag & index & "000";
          adMemDataWrite <= miss_rdata(31 downto 0);
          

--*********************************************************************************************
--    DRAINING
--*********************************************************************************************

      when STARTDR => 
        nextstate <= DRAIN;
        startdrain <= '1';
                  if (c_rdata0(74 downto 73) = "11") then
            adMemWrite <= '1';
            end if;
            adMemAddr <= c_rdata0(72 downto 64) & drainindex(4 downto 0) & "00";
            adMemDataWrite <= c_rdata0(31 downto 0);

      when DRAIN =>
        nextstate <= DRAIN;
        startdrain <= '1';
        
        if (drainindex(5) =  '0') then --set 0
          if (c_rdata0(74 downto 73) = "11") then
            adMemWrite <= '1';
          end if;
          adMemAddr <= c_rdata0(72 downto 64) & drainindex(4 downto 0) & "00";
          if (drainindex(0) = '0') then --block 0
            adMemDataWrite <= c_rdata0(31 downto 0);
          else
            adMemDataWrite <= c_rdata0(63 downto 32);
          end if;
        else --set 1
          if (c_rdata1(74 downto 73) = "11") then
            adMemWrite <= '1';
          end if;
          if (drainindex(0) = '0') then
            adMemAddr <= c_rdata1(72 downto 64)  & drainindex(4 downto 1) & '0' & "00";
            adMemDataWrite <= c_rdata1(31 downto 0);
          else
            adMemAddr <= c_rdata1(72 downto 64)  & drainindex(4 downto 1) & '1' & "00";
            adMemDataWrite <= c_rdata1(63 downto 32);
          end if;
        end if;
        if (drainindex = "111111" and adMemWait = '0') then
          nextstate <= FINISHDRAIN;
        end if;
        
        when FINISHDRAIN =>
          nextstate <= HALT;
          if (c_rdata1(74 downto 73) = "11") then
              adMemWrite <= '1';
            end if;
          adMemAddr <= c_rdata1(72 downto 64) & drainindex(4 downto 1) & "100";
          adMemDataWrite <= c_rdata1(63 downto 32);
        
      when HALT =>
        nextstate <= HALT;
        cpuhalt <= '1';


--*****************************************************************************************
--                SNOOP CONTORL        
--_________________________________________________________________________________________        

    when SNOOPWB0 =>
        snoop_valid <= '1';
        if (dMemRead = '1' or dMemWrite = '1') then
          dMemWait <= '1';
        end if;

        adMemAddr <= snoop_tag & index & "000";          
        adMemDataWrite <= snoop_rdata(31 downto 0);
        adMemWrite <= '1';

        if (adMemWait = '1') then
          nextstate <= SNOOPWB0;
        else
          nextstate <= SNOOPFINISH0;
        end if;
        
    when SNOOPFINISH0 =>
      snoop_valid <= '1';
        if (dMemRead = '1' or dMemWrite = '1') then
          dMemWait <= '1';
        end if;
        nextstate <= SNOOPWB1;
        adMemWrite <= '1';
        adMemAddr <= snoop_tag & index & "000";
        adMemDataWrite <= snoop_rdata(31 downto 0);

      
    when SNOOPWB1 =>
      snoop_valid <= '1';
        if (dMemRead = '1' or dMemWrite = '1') then
          dMemWait <= '1';
        end if;

        adMemWrite <= '1';
        adMemAddr <= snoop_tag & index & "100";
        adMemDataWrite <= snoop_rdata(63 downto 32);
        
        if (adMemWait = '1') then
          nextstate <= SNOOPWB1;
        else
          nextstate <= SNOOPFINISH1;
        end if;

    when SNOOPFINISH1 =>
        snoop_valid <= '1';
        if (dMemRead = '1' or dMemWrite = '1') then
          dMemWait <= '1';
        end if;

        if (snoop_write = '1') then
            --invalidate M -> I
          snoop_en <= '1';
          wdata <= '0' & snoop_rdata(73 downto 0); --set valid bit to 0
        else
            --clean  M -> S
          snoop_en <= '1';
          wdata <= snoop_rdata(74) & '0' & snoop_rdata(72 downto 0);
        end if;

        nextstate <= IDLE;
        adMemWrite <= '1';
        adMemAddr <= snoop_tag & index & "100";
        adMemDataWrite <= snoop_rdata(63 downto 32);
        
      when others => 
        nextstate <= IDLE;
        
      end case;

  end process next_state;




--when link and dMemWrite
--  if link_reg == dMemAddr && its valid
--  store dMemDataWrite 
--  output x"1" on dMemDataRead
--else
--  output x"0" on dMemDataRead




with dMemDataRead_select select
  dMemDataRead <= data0(31 downto 0)  when "010",
                  data0(63 downto 32) when "110",
                  data1(31 downto 0)  when "001",
                  data1(63 downto 32) when "101",
                  x"00000001" when "111",
                  x"00000000" when "000",
                  x"BDDABDDA" when others;


--cpuhalt <= '1' when drainindex = "111111"
  --        else '0';

drainP : process(CLK, nReset, adhalt, drainindex, adMemWait, startdrain)

begin
  if nReset = '0' then
    drainindex <= "000000";
  elsif falling_edge(CLK) then
    
    if drainindex = "111111" then
      drainindex <= drainindex;
    elsif adhalt = '1' and adMemWait = '0' and startdrain = '1' then
      drainindex <= next_drainindex;
              
    end if;    
  end if;
end process drainP;

next_drainindex <= drainindex + 1;

--statereg : process(CLK, nReset)

--begin
 -- if nReset = '0' then
 --   state <= IDLE;
 -- elsif rising_edge(CLK) then
  --  state <= nextstate;
  --end if;
--end process statereg;

end struct;